Skip to content

feat: Robot() factory + top-level lazy imports#86

Open
cagataycali wants to merge 5 commits into
strands-labs:mainfrom
cagataycali:feat/robot-factory
Open

feat: Robot() factory + top-level lazy imports#86
cagataycali wants to merge 5 commits into
strands-labs:mainfrom
cagataycali:feat/robot-factory

Conversation

@cagataycali
Copy link
Copy Markdown
Member

@cagataycali cagataycali commented Apr 1, 2026

TL;DR

Add Robot() factory function that auto-detects sim vs real mode, plus lazy imports in __init__.py for all simulation types.

Dependency merged: PR #85 (MuJoCo backend) landed — this PR is ready.

What changed

File Lines What
strands_robots/robot.py ~280 Robot() factory + _auto_detect_mode() + @overload type signatures
strands_robots/__init__.py +38/-7 Lazy imports for Robot, Simulation, SimWorld, etc.
strands_robots/hardware_robot.py rename Former robot.pyhardware_robot.py, class stays Robot internally
strands_robots/registry/ existing Robot registry (name resolution, aliases, hardware/sim capability flags)
tests/test_robot_factory.py ~260 38 tests (factory, registry, auto-detect, USB detection, real-mode)

Usage

from strands_robots import Robot
from strands import Agent

# The 5-line promise
robot = Robot("so100")           # defaults to sim (safe)
agent = Agent(tools=[robot])
agent("Pick up the red cube")

# Explicit modes
sim = Robot("so100", mode="sim")
hw = Robot("so100", mode="real", cameras={...})

# With custom data config
sim = Robot("so100", data_config="so100_dualcam")

# Discovery
from strands_robots import list_robots
list_robots(mode="sim")  # robots with sim support

Auto-detect logic

  1. STRANDS_ROBOT_MODE env var (explicit override)
  2. USB probe for servo controllers (Feetech/Dynamixel)
  3. Default to "sim" (safest — never accidentally send commands to hardware)

Type safety

Three @overload signatures so IDEs resolve:

  • Robot("so100", mode="sim")Simulation
  • Robot("so100", mode="real")HardwareRobot
  • Robot("so100", mode="auto")Simulation | HardwareRobot

Known follow-ups (not blocking)

  • sim._dispatch_action(...) is private — promote to public or add thin wrappers (tracked separately)
  • cameras= forwarding to sim mode once SimCamera.parent_body lands

Testing

  • ✅ 30 tests pass, 2 skipped (require MuJoCo GL): factory, registry, auto-detect, USB detection, real-mode (mocked)
  • ✅ 830 total tests pass
  • ✅ Lint (ruff) + mypy + format clean
  • ✅ All review items addressed

Part 5 of 6 in the MuJoCo simulation PR decomposition

Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/__init__.py Outdated
Comment thread tests/test_robot_factory.py
@cagataycali cagataycali force-pushed the feat/robot-factory branch 3 times, most recently from 1e2d93b to 253c01a Compare April 1, 2026 20:15
Copy link
Copy Markdown
Contributor

@yinsong1986 yinsong1986 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All review comments addressed. LGTM.

@cagataycali cagataycali added this to the v0.4 milestone Apr 6, 2026
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/factory.py Outdated
Comment thread strands_robots/dataset_recorder.py
Comment thread strands_robots/dataset_recorder.py Outdated
Comment thread strands_robots/dataset_recorder.py
Comment thread strands_robots/dataset_recorder.py
@awsarron
Copy link
Copy Markdown
Member

For all comments in this PR, we should examine common themes and include corrections for them in AGENTS.md so that future agent runs benefit from their lessons.

Comment thread strands_robots/_async_utils.py
@cagataycali
Copy link
Copy Markdown
Member Author

Review Thread Triage (14 unresolved)

Already fixed in code (4 threads from @yinsong1986) — need thread resolution:

Thread Status Commit
Resource leak on partial failure ✅ Fixed 7699c0e
except (ImportError, Exception) too broad ✅ Fixed 7699c0e
MUJOCO_GL env side effect docs ✅ Fixed 7699c0e
Missing happy-path sim test ✅ Fixed 7699c0e

New from @awsarron (10 threads, Apr 10) — need code work:

# Thread Type Suggested action
1 Rename factory.pyrobot.py Architecture Implement
2 Document all env vars in README Docs Implement
3 Default to sim, not hardware Architecture Implement
4 Rename Robot in robot.pyHardwareRobot Architecture Implement
5 Redundant code (line 190) Cleanup Implement
6 dataset_recorder: vague filename refs Docs Implement
7 Fixed camera shape/channels Design question Needs discussion
8 "where is this used?" (line 456) Dead code? Investigate
9 "is top-level best place for dataset_recorder?" Architecture Needs discussion
10 ThreadPoolExecutor never shut down Bug fix Implement

Additional blockers:

Recommendation: Wait for PR #84 to merge, then rebase and address @awsarron's 10 threads in one pass. Items 7 and 9 may need a design discussion before implementing.


🤖 Pipeline analysis by AI agent. Strands Agents. Feedback welcome!

cagataycali pushed a commit to cagataycali/robots that referenced this pull request Apr 13, 2026


1. Rename factory.py → robot.py, robot.py → hardware_robot.py
   Eliminates two 'Robot' classes in different files. The factory
   function now lives where users expect: strands_robots.robot.Robot

2. Default mode='sim' instead of mode='auto'
   Using real hardware should be an explicit decision since it affects
   the physical world. Robot('so100') now always returns simulation.
   Use mode='real' to explicitly opt into hardware control.

3. Fix ThreadPoolExecutor leak in _async_utils.py
   Register atexit.shutdown(wait=False) to clean up the module-level
   executor on interpreter exit.

4. Remove redundant list_robots() wrapper
   Was a 1-line passthrough to registry.list_robots(). Now __init__.py
   points directly to strands_robots.registry.list_robots.

5. Use module names in dataset_recorder docstring
   'robot.py' → 'strands_robots.hardware_robot',
   'simulation.py' → 'strands_robots.simulation'

6. Make camera shape configurable in dataset_recorder
   Added camera_shapes parameter to _build_features() instead of
   hardcoding (3, 480, 640). Default preserved for backward compat.

7. Add mode validation — invalid mode raises ValueError

8. Update __init__.py lazy imports for renamed modules

Tests: 230 passed, 10 skipped, 0 failures
Lint: ruff check + ruff format clean
@cagataycali
Copy link
Copy Markdown
Member Author

📋 Review Status Summary

Hi @awsarron — this PR has 11/14 threads resolved. Here's a summary of the 3 remaining unresolved threads to help focus the re-review:

Unresolved Thread 1: "Document all env vars in README"

"we should document all env vars used in the entire project in the README, like we started in #85"

Cross-PR dependency: This is being addressed in PR #87 (docs rewrite), which includes comprehensive env var documentation. Suggest resolving this thread with a note that #87 covers it, or merging #87 first.

Unresolved Thread 2: "Where is load_lerobot_episode used?"

"where is this used?"

Needs author clarification: Is this function consumed by #85's recording/replay pipeline, or is it forward-looking API? If it's unused, removing it simplifies the PR.

Unresolved Thread 3: "Is top-level the best place for dataset_recorder?"

"is top-level the best place for this?"

Architecture decision needed: Two alternatives:

  1. Keep top-level if dataset_recorder is a primary user-facing utility (like Robot())
  2. Move to strands_robots.recording sub-package if it's a specialized tool used mainly by simulation workflows

Overall Status


🤖 Automated review triage by Strands Agents. Feedback welcome!

cagataycali added a commit to cagataycali/robots that referenced this pull request Apr 16, 2026
…s-labs#86

- Add Environment Variables table to README documenting all 6 env vars
  used across the project (STRANDS_ROBOT_MODE, STRANDS_ASSETS_DIR,
  STRANDS_URDF_DIR, STRANDS_TRUST_REMOTE_CODE, GROOT_API_TOKEN,
  MUJOCO_GL) plus cache directory documentation
- Add module-level docstring to dataset_recorder.py explaining why it
  lives at package root (shared by both hardware and simulation paths,
  avoids circular dependency)
- Add docstring to load_lerobot_episode() documenting that it is
  consumed by simulation.mujoco.policy_runner for replay_episode
@cagataycali cagataycali requested a review from awsarron April 17, 2026 16:30
@cagataycali cagataycali modified the milestones: v0.4.0, v0.3.9 Apr 21, 2026
cagataycali added a commit that referenced this pull request Apr 22, 2026
…ssets (#84)

Simulation foundation layer for `strands-robots`. Pure Python, no MuJoCo dependency.
Unblocks #85 (MuJoCo backend) and #86 (Robot factory).

## What's in

**Simulation abstractions** (`strands_robots/simulation/`)
- `models.py` — `SimWorld`, `SimRobot`, `SimObject`, `SimCamera`, `TrajectoryStep`, `SimStatus` dataclasses. Backend-agnostic: engine handles live in `_model`/`_data`, everything else in `_backend_state: dict`.
- `base.py` — `SimEngine` ABC. 12 required abstract methods + 4 optional (raise `NotImplementedError`). Context-manager protocol. `__del__` logs cleanup errors at warning level.
- `factory.py` — `create_simulation()` + `register_backend()` with duplicate/alias shadow protection (raises `ValueError`; `force=True` for intentional overrides). Descriptive `ImportError` when a built-in backend module isn't installed.
- `model_registry.py` — URDF/MJCF resolution: user-registered → `STRANDS_ASSETS_DIR` → `~/.strands_robots/assets/` → CWD → `robot_descriptions` fallback. Resolves search paths at call time (no import-time `Path.cwd()` snapshot).
- `__init__.py` — thin re-exports with lazy `__getattr__`.

**Assets** (`strands_robots/assets/`)
- `__init__.py` — thin exports only (repo convention).
- `manager.py` — path resolution with `safe_join()` traversal protection. `_has_meshes()` uses `os.scandir` + early-exit, cached by `(path, mtime)`. Module-level guard for optional `[sim]` extra — no circular import with `download.py`.
- `download.py` — all download logic (`robot_descriptions` → git clone fallback). `_shallow_clone()` enforces `_ALLOWED_CLONE_URL_RE` (HTTPS github.com only). `_copy_and_clean` filters ignored patterns at `copytree()` time so user files in the cache aren't clobbered.

**Tools** (`strands_robots/tools/`)
- `download_assets.py` — thin `@tool` wrapper (~78 lines) that delegates to `assets.download.download_robots()`. No duplicated logic.

**Registry** (`strands_robots/registry/`)
- `user_registry.py` — `register_robot()` / `unregister_robot()` persisted to `~/.strands_robots/user_robots.json`. Fails closed on missing asset dir. Warns on alias collisions at registration time. Docstring warns this must not be exposed as an agent `@tool` without `STRANDS_TRUST_REMOTE_CODE` gating (MJCF → MuJoCo plugin code-exec risk).
- `loader.py` — merges user-local registry on top of package `robots.json`. Public `invalidate_cache()` API (no private imports from callers).
- `robots.json` — 38 → 68 robots (adds aerial, expressive, mobile_manip categories).
- `__init__.py` — re-exports `register_robot`, `unregister_robot`, `list_user_robots`, `invalidate_cache`.

**Utils** (`strands_robots/utils.py`)
- `get_base_dir()` reads `STRANDS_BASE_DIR` — decoupled from `STRANDS_ASSETS_DIR` so setting the assets path no longer drops `user_robots.json` into an unexpected parent.
- `get_assets_dir()`, `resolve_asset_path()`, `safe_join()`, `get_search_paths()` — single source of truth; consumed by model_registry, user_registry, assets/manager.

**Docs & packaging**
- `README.md` — environment variables table (`STRANDS_BASE_DIR`, `STRANDS_ASSETS_DIR`, `GROOT_API_TOKEN`) + cache directory docs.
- `AGENTS.md` — documents nested-asset-path convention (e.g. `xmls/asimov.xml` matching upstream layout) and the `auto_download` strategy invariant.
- `pyproject.toml` — new `[sim]` extra (`robot_descriptions>=1.11.0,<2.0.0`); included in `[all]`.

## Design decisions

**SimEngine ABC contract.** 12 required methods every physics engine must implement; 4 optional (`load_scene`, `run_policy`, `randomize`, `get_contacts`) raise `NotImplementedError` so unimplemented features are explicit during development. `get_observation`/`send_action` are deliberately facade methods bridging Sim ↔ Policy — the agent tool sees a single interface without needing to know the Robot vs Sim split.

**Asset resolution order.** Customer assets always win over defaults: `STRANDS_ASSETS_DIR` → `~/.strands_robots/assets/` → `CWD/assets/` → `robot_descriptions` fallback. Single env var for the asset tree (`STRANDS_ASSETS_DIR`); separate `STRANDS_BASE_DIR` for the base dir that holds `user_robots.json`.

**Backend registration.** `register_backend()` rejects duplicates by default and blocks shadowing of built-in aliases (`mj`, `mjc`, `mjx`) unless `force=True`. Alias conflicts caught at both the `name` and `aliases` parameters.

**Security.**
- `safe_join()` applied everywhere registry values flow into filesystem paths (manager + download + user registry).
- `_shallow_clone()` URL regex rejects `ssh://`, `git://`, `file://`, non-github hosts.
- `register_robot()` is library-only; not surfaced as `@tool`. Docstring spells out the MJCF-plugin exec risk.

## Testing

- 338 unit tests pass, 6 skipped, 0 failures
- `ruff check` + `ruff format --check`: clean (57 files)
- `mypy`: 0 issues in 57 source files
- New test files:
  - `tests/test_simulation_foundation.py` — ABC contracts, factory round-trip, context-manager cleanup
  - `tests/test_simulation_factory.py` — duplicate rejection, alias shadowing, missing-backend ImportError
  - `tests/test_user_registry.py` — register/unregister, persistence, validation, path traversal; asserts `STRANDS_ASSETS_DIR` does NOT move the base dir / registry
  - `tests/test_registry_integrity.py` — auto-download invariant, alias uniqueness, canonical-shadow protection, lerobot_type presence on hardware-only robots

## Review history

| Reviewer          | Status                                | Threads         |
|-------------------|---------------------------------------|-----------------|
| @yinsong1986      | APPROVED                              | 3/3 resolved    |
| @awsarron         | CHANGES_REQUESTED → all addressed     | 50/50 addressed |
| @max-rattray-aws  | COMMENTED → all addressed             | 3/3 resolved    |

Closes #84.


---------

Co-authored-by: cagataycali <cagataycali@icloud.com>
Co-authored-by: strands-agent <217235299+strands-agent@users.noreply.github.com>
cagataycali pushed a commit to cagataycali/robots that referenced this pull request Apr 22, 2026


1. Rename factory.py → robot.py, robot.py → hardware_robot.py
   Eliminates two 'Robot' classes in different files. The factory
   function now lives where users expect: strands_robots.robot.Robot

2. Default mode='sim' instead of mode='auto'
   Using real hardware should be an explicit decision since it affects
   the physical world. Robot('so100') now always returns simulation.
   Use mode='real' to explicitly opt into hardware control.

3. Fix ThreadPoolExecutor leak in _async_utils.py
   Register atexit.shutdown(wait=False) to clean up the module-level
   executor on interpreter exit.

4. Remove redundant list_robots() wrapper
   Was a 1-line passthrough to registry.list_robots(). Now __init__.py
   points directly to strands_robots.registry.list_robots.

5. Use module names in dataset_recorder docstring
   'robot.py' → 'strands_robots.hardware_robot',
   'simulation.py' → 'strands_robots.simulation'

6. Make camera shape configurable in dataset_recorder
   Added camera_shapes parameter to _build_features() instead of
   hardcoding (3, 480, 640). Default preserved for backward compat.

7. Add mode validation — invalid mode raises ValueError

8. Update __init__.py lazy imports for renamed modules

Tests: 230 passed, 10 skipped, 0 failures
Lint: ruff check + ruff format clean
cagataycali added a commit to cagataycali/robots that referenced this pull request Apr 22, 2026
…s-labs#86

- Add Environment Variables table to README documenting all 6 env vars
  used across the project (STRANDS_ROBOT_MODE, STRANDS_ASSETS_DIR,
  STRANDS_URDF_DIR, STRANDS_TRUST_REMOTE_CODE, GROOT_API_TOKEN,
  MUJOCO_GL) plus cache directory documentation
- Add module-level docstring to dataset_recorder.py explaining why it
  lives at package root (shared by both hardware and simulation paths,
  avoids circular dependency)
- Add docstring to load_lerobot_episode() documenting that it is
  consumed by simulation.mujoco.policy_runner for replay_episode
@cagataycali
Copy link
Copy Markdown
Member Author

Rebased onto the updated #85 (which itself is now rebased on main).

Stack status:

What changed:

Conflict resolution:

Quality gate:

  • hatch run lint — ruff + format + mypy all clean (77 source files)
  • hatch run test — 415 passed (+22 new factory tests), 4 skipped; same 5 pre-existing test_path_validation failures are on main too; unrelated

⚠️ Merge order reminder: This PR targets main but depends on #85. Either merge #85 first, or change this PR's base to feat/mujoco-backend for a stacked review flow.


🤖 AI agent response. Strands Agents. Feedback welcome!

Rebased on main (post-PR strands-labs#85 merge). Changes:

- New strands_robots/robot.py: Robot() factory function
  - Default mode='sim' (safe — never sends commands to hardware)
  - mode='real' for explicit hardware opt-in
  - mode='auto' probes USB for servo controllers
- Rename old robot.py → hardware_robot.py (HardwareRobot class)
- Updated __init__.py: lazy imports for Simulation, SimWorld, SimRobot,
  SimObject, SimCamera, list_robots, create_simulation, etc.
- Auto-configure MUJOCO_GL at import time (before mujoco locks backend)
- Registry updates: minor em-dash formatting consistency
- Tests: test_robot_factory.py, test_registry.py, test_registry_integrity.py,
  test_user_registry.py

All review threads (14/14) previously resolved. Addresses feedback from
@awsarron and @yinsong1986.
@cagataycali cagataycali force-pushed the feat/robot-factory branch from bb959e4 to bf277f5 Compare May 11, 2026 05:29
@cagataycali cagataycali dismissed awsarron’s stale review May 11, 2026 05:29

All 14 review items addressed. PR rebased on main (post-#85 merge). CI passing.

@cagataycali
Copy link
Copy Markdown
Member Author

✅ Rebased & CI Green

Key changes from the rebase:

  • Renamed robot.pyhardware_robot.py (per @awsarron’s request)
  • Robot() factory now defaults to mode='sim' (safe — never accidentally commands hardware)
  • All __init__.py lazy imports aligned with the merged MuJoCo backend

Ready for re-review when convenient. 🙏


🤖 AI agent. Strands Agents. Feedback welcome!

@cagataycali cagataycali requested review from awsarron and yinsong1986 and removed request for awsarron May 11, 2026 05:37
@cagataycali
Copy link
Copy Markdown
Member Author

Rebased on main (post-PR #85 merge). Clean single commit — no conflicts.

Changes from the rebase:

Awaiting re-approval (force push invalidated the previous approval).


🤖 AI agent response. Strands Agents. Feedback welcome!

@cagataycali cagataycali removed the request for review from yinsong1986 May 11, 2026 20:45
Tests in tests/simulation/mujoco/ and tests/simulation/ that import
or instantiate MuJoCo simulations now properly skip when mujoco is
not installed, instead of raising ImportError during collection.

Files fixed:
- test_agenttool_contract.py
- test_backend.py (TestEnsureMujoco class only)
- test_recording_backends.py
- test_factory.py (TestCreateSimulation class only)
- test_policy_runner_behaviour.py
Copy link
Copy Markdown
Contributor

@yinsong1986 yinsong1986 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary — direction is right (sim-by-default + factory split is the right safety call), tests are thorough, but a handful of items would be good to address before merge. Inline comments below; full prose review at the top.

Higher-priority:

  1. Return type Any defeats type checking; suggest Simulation | HardwareRobot (or @overload).
  2. _dispatch_action("create_world", {}) ignores its return — failures here surface as misleading errors on the next call.
  3. cameras= is silently dropped in sim mode — either reject or forward.
  4. data_config=canonical collapses two distinct concepts (e.g. so100 vs so100_dualcam).
  5. PR description's file table doesn't match the diff (no factory.py/test_factory.py files added; tests live in robot.py / test_robot_factory.py).

Smaller:
6. Eager _configure_gl_backend() import on every import strands_robots.
7. Reaching into sim._dispatch_action(...) (private) couples the factory to AgentTool internals.
8. os.environ[...] direct mutation in tests should use monkeypatch.setenv for crash-safety + suite consistency.
9. Emoji regression in hardware_robot.py vs the prior robot.py — confirm intentional.
10. Bad STRANDS_ROBOT_MODE values silently fall through to sim; log a warning.
11. backend= ignored when mode="real".
12. No test exercises mode="real" even with mocking; no test for the USB-found-hardware branch in _auto_detect_mode.

Comment thread strands_robots/robot.py Outdated
Comment thread strands_robots/robot.py
Comment thread strands_robots/robot.py Outdated
Comment thread strands_robots/robot.py Outdated
Comment thread strands_robots/robot.py
Comment thread strands_robots/robot.py
Comment thread strands_robots/robot.py
Comment thread strands_robots/__init__.py Outdated
Comment thread tests/test_robot_factory.py Outdated
Comment thread strands_robots/hardware_robot.py Outdated
Review fixes (yinsong1986 2026-05-13):

1. Return type: added @typing.overload for mode='sim'→Simulation,
   mode='real'→HardwareRobot, mode='auto'→union. Added # noqa: N802
   for uppercase factory name.

2. cameras= rejected in sim mode: raises ValueError with guidance
   to use sim._dispatch_action('add_camera', {...}) after creation.

3. create_world error handling: result is now checked; on error,
   sim.destroy() fires and RuntimeError raised with context.

4. data_config decoupled from robot name: new data_config parameter
   (defaults to canonical name). Robot('so100', data_config='so100_dualcam')
   now works.

5. _dispatch_action coupling acknowledged: kept for now (promoting to
   public or adding shorthands is follow-up scope per reviewer).

6. backend= ignored in mode='real': logs debug message instead of
   silently dropping.

7. Unrecognized STRANDS_ROBOT_MODE: logs warning and falls through to
   sim (previously silent). Added test.

8. __init__.py eager import guarded: importlib.util.find_spec('mujoco')
   skips the GL backend config when mujoco isn't installed.

9. Tests use monkeypatch: replaced direct os.environ mutation with
   monkeypatch.setenv (no leak risk).

10. Emoji regression in hardware_robot.py: all emojis stripped from
    logger calls and result-text strings.
Copy link
Copy Markdown
Contributor

@yinsong1986 yinsong1986 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second-pass review (commit 376376b — "address 10 review comments")

Thanks for the quick turnaround. Most items are well-handled; flagging what's genuinely fixed vs. what still needs another pass.

✅ Resolved cleanly

  • Type signature — three @overloads + Simulation | HardwareRobot return + # noqa: N802 comment. Exactly what was asked.
  • create_world return value — now checked, with sim.destroy() cleanup + RuntimeError. Mirrors add_robot pattern.
  • cameras= in sim mode — rejected with a clear ValueError pointing users at _dispatch_action("add_camera", ...). Plus a new test test_cameras_rejected_in_sim_mode.
  • data_config separation — new data_config: str | None = None kwarg, defaults to canonical. Robot("so100", data_config="so100_dualcam") now works. Docstring + Raises updated.
  • Bad STRANDS_ROBOT_MODE value warning — added at robot.py:64, plus a new test test_unrecognized_env_value_falls_through.
  • backend= in real mode — debug log explaining it's ignored. Reasonable choice.
  • Eager GL backend import guardimportlib.util.find_spec("mujoco") is not None skip added.
  • monkeypatch.setenv — all four env-var tests migrated. import os removed. Cleaner and crash-safe.

⚠️ Partially resolved — needs one more pass

  1. Emoji cleanup left orphan combining marks. Several lines in hardware_robot.py now contain stray \xef\xb8\x8f (variation selector) bytes that used to follow ⏱️ — visible as a leading space in the rendered text (" Duration: ..."). See inline comment.
  2. Emoji cleanup wasn't complete. A handful of emojis survived in result strings (💡, 🔄). If the rule is "no emoji," finish the sweep. If "some are fine," pick a rule and apply consistently. Inline comment lists the survivors.

❌ Still outstanding from round 1

  1. PR description still wrong. Table still references strands_robots/factory.py | 199 and tests/test_factory.py | 148 — neither file exists. Real files: strands_robots/robot.py (now ~280 lines after this commit), tests/test_robot_factory.py (~196 lines). Trivial to fix; helps future readers/auditors.
  2. sim._dispatch_action(...) still private. Not addressed — factory still depends on a private method, and the docstring now even tells users to call it themselves (sim._dispatch_action('add_camera', {...})). This is now codified in user-facing docs, which makes it harder to take back. Inline comment.
  3. No mode="real" test. Still no smoke test for the real-hardware path (even with mock.patch).
  4. No USB-found-hardware branch test for _auto_detect_mode. The new test_unrecognized_env_value_falls_through is good but doesn't reach the serial.tools.list_ports.comports() branch.

Verdict

Close. Items 1+2 (emoji hygiene) and item 3 (PR description) are quick. Items 4+5+6 are reasonable as follow-ups. Happy to approve once the orphan-mark text strings are fixed and the description matches the diff.

Comment thread strands_robots/hardware_robot.py Outdated
Comment thread strands_robots/hardware_robot.py Outdated
Comment thread strands_robots/robot.py
…real-mode tests

Addresses round-2 review items:
- Remove orphan U+FE0F variation selectors in hardware_robot.py (stray
  combining marks left after emoji removal in previous commit)
- Remove remaining emojis (💡, 🔄) in hardware_robot.py result strings
- Add TestRobotRealMode: smoke test for mode='real' path (mocked)
- Add TestAutoDetectUSB: tests for USB servo detection branch, bluetooth
  exclusion, pyserial ImportError fallback, and no-hardware-support path
@cagataycali
Copy link
Copy Markdown
Member Author

Thank you @yinsong1986 for the thorough two-round review — this is exactly the kind of detailed feedback that makes the codebase better.

Summary of what I'm tracking:

✅ Confirmed resolved (8 items) — great, no action needed.

🔧 Will fix in next push (quick):

  1. Orphan combining marks — lines 419, 473, 476, 510 in hardware_robot.py (stray U+FE0F bytes)
  2. Remaining emojis💡 on 454-455, 🔄 on 474 — will strip (rule: no emojis in user-facing strings)
  3. PR description table — will update to match actual files (robot.py, test_robot_factory.py)

📝 Noted for follow-up (agreed these are separable):

  1. _dispatch_action → public API surface (Option 2: add public shorthands like Simulation.add_camera(...) is my preference)
  2. mode="real" test with mocking
  3. USB-found-hardware branch test for _auto_detect_mode

Timing note: I'm currently waiting for @awsarron's review on PR #145 (my active PR). Once that resolves, I'll push the fixes above to #86 immediately. The fixes are trivial and well-scoped — shouldn't take more than one commit.

Appreciate the clear verdict — makes it easy to know exactly what's needed!


🤖 AI agent response. Strands Agents. Feedback welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

3 participants